package org.hjl.commonutils.idempotent.service.impl;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.hjl.commonutils.idempotent.Idempotent;
import org.hjl.commonutils.idempotent.service.KeyResolver;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;

import java.lang.reflect.Method;

/**
 * @author hjl
 * 从注解的方法的参数中解析出用于幂等性处理的键值（key）
 */
public class ExpressionResolver implements KeyResolver {

    private static final SpelExpressionParser PARSER = new SpelExpressionParser();

    private static final LocalVariableTableParameterNameDiscoverer DISCOVERER = new LocalVariableTableParameterNameDiscoverer();

    @Override
    public String resolver(Idempotent idempotent, JoinPoint point) {
        //获取被拦截方法的所有参数
        Object[] arguments = point.getArgs();
        //从字节码的局部变量表中解析出参数名称
        String[] params = DISCOVERER.getParameterNames(getMethod(point));
        //SpEL表达式执行的上下文环境，用于存放变量
        StandardEvaluationContext context = new StandardEvaluationContext();
        //遍历方法参数名和对应的参数值，将它们一一绑定到StandardEvaluationContext中。
        //这样SpEL表达式就可以引用这些参数值
        if (params != null && params.length > 0) {
            for (int len = 0; len < params.length; len++) {
                context.setVariable(params[len], arguments[len]);
            }
        }
        //使用SpelExpressionParser来解析Idempotent注解中的key属性，将其作为SpEL表达式字符串
        Expression expression = PARSER.parseExpression(idempotent.key());
        //转换结果为String类型返回
        return expression.getValue(context, String.class);
    }

    /**
     * 根据切点解析方法信息
     *
     * @param joinPoint 切点信息
     * @return Method 原信息
     */
    private Method getMethod(JoinPoint joinPoint) {
        //将joinPoint.getSignature()转换为MethodSignature
        //Signature是AOP中表示连接点签名的接口，而MethodSignature是它的具体实现，专门用于表示方法的签名。
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        //获取到方法的声明。这将返回代理对象所持有的方法声明。
        Method method = signature.getMethod();

        //判断获取到的方法是否属于一个接口
        //因为在Java中，当通过Spring AOP或其它代理方式调用接口的方法时，实际被执行的对象是一个代理对象，直接获取到的方法可能来自于接口声明而不是实现类。
        if (method.getDeclaringClass().isInterface()) {
            try {
                //通过反射获取目标对象的实际类（joinPoint.getTarget().getClass()）中同名且参数类型相同的方法
                //这样做是因为代理类可能对方法进行了增强，直接调用实现类的方法可以确保获取到最准确的实现细节
                method = joinPoint.getTarget().getClass().getDeclaredMethod(joinPoint.getSignature().getName(),
                        method.getParameterTypes());
            } catch (SecurityException | NoSuchMethodException e) {
                throw new RuntimeException(e);
            }
        }
        return method;
    }

}